/** @file usb.c
  *
  * @brief This file contains the usb device function calls
  *
  *  Copyright (C) 2024, Artosyn Technologies. ALL RIGHTS RESERVED.
  *
  *  This Information is proprietary to Artosyn Technologies and MAY NOT
  *  be copied by any method or incorporated into another program without
  *  the express written consent of Artosyn. This Information or any portion
  *  thereof remains the property of Artosyn. The Information contained herein
  *  is believed to be accurate and Artosyn assumes no responsibility or
  *  liability for its use in any way and conveys no license or title under
  *  any patent or copyright and makes no representation or warranty that this
  *  Information is free from patent or copyright infringement.
  *
***/

/****************************
 * Include
 ****************************/
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/vmalloc.h>
#include <linux/usb.h>

#include "bus/usb.h"
#include "utils/utils.h"
#include "rpc/ar_rpc.h"
#include "oal_main.h"
#include "oal_workqueue.h"
#include "oal_dnld.h"

/****************************
 * Extern Definition
 ****************************/
extern bool autopm_on;

/****************************
 * Local Definition
 ****************************/
/*-------------------USB Configuration-------------------*/
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
#define USB_SPEED_SUPER_PLUS    5
#endif

/** Type of endpoint */
#define AR_USB_EPTYPE_CTRL  0x00
#define AR_USB_EPTYPE_ISOC  0x01
#define AR_USB_EPTYPE_BULK  0x02
#define AR_USB_EPTYPE_INT   0x03

/** EP number of Artosyn system */
#define AR_USB_NUM_OF_EP    2

/** Id of endpoint */
#define AR_USB_EP_RX        1
#define AR_USB_EP_TX        1
#define AR_USB_EP_MSG_I     1
#define AR_USB_EP_MSG_O     1

/** USB auto pm delay */
#define AR_USB_AUTOPM_DELAY 5000

/** Config of usb endpoint */
struct ar_usb_ep_cfg {
    int num;    //endpoint number
    int type;   //endpoint type(0:Control, 1:Isochronous, 2:Bulk, 3:Interrupt)
    int dir;    //endpont direction(0:Out, 1:In)
};

/** USB info of artosyn romcode */
#define ARTO_ROMCODE_VID        (0x4152)
#define ARTO_ROMCODE_PID        (0x8030)
#define ARTO_ROMCODE_NAME       "Rom_usb"

/** USB info of artosyn rtos */
#define ARTO_RTOS_VID           (0x1d6b)
#define ARTO_RTOS_PID           (0x8030)
#define ARTO_RTOS_NAME          "Artosyn in HS Mode"

/*-------------------USB macro-------------------*/
/** Define lock/unlock wrapper */
/** Initialize semaphore */
#define udev_req_init(d)         sema_init(&d->req_lock, 1)
#define udev_req_locked_init(d)  sema_init(&d->req_lock, 0)

/** Acquire semaphore and with blocking */
#define udev_req_lock(d)         down_interruptible(&d->req_lock)

/** Release semaphore */
#define udev_req_unlock(d)       up(&d->req_lock)

/** Initialize mutex */
#define udev_mutex_init(d)       mutex_init(&d->mutex)

/** mutex lock */
#define udev_mutex_lock(d)       mutex_lock(&d->mutex)

/** mutex unlock*/
#define udev_mutex_unlock(d)     mutex_unlock(&d->mutex)

/*-------------------USB module structure-------------------*/
enum ar_usb_state {
    AR_USB_STATE_DOWN,          /** USB is not inited or shutdown */
    AR_USB_STATE_UP,            /** USB is running */
    AR_USB_STATE_SLEEP          /** USB is in sleep */
};

typedef enum ar_urb_state {
    URB_STATE_INITIALIZED,      /** URB is initialized */
    URB_STATE_SUBMITTED,        /** URB is submitted to kernel */
    URB_STATE_KILLING,          /** URB is in killing */
    URB_STATE_KILLED            /** URB is in killed */
} ar_urb_state_e;

struct ar_udev {
    /** module */
    struct usb_device *usbdev;              /** Point to USB device */
    struct device *dev;                     /** Device of USB(udev->dev) */
    struct usb_interface *intf;             /** Point to USB interface */
    oal_handle *handle;                     /** Point to oal handler */

    /** status, sync */
    enum ar_usb_state state;                /** Status of USB device */

    // struct completion dev_init_lock;
    /** udev mutex */
    struct mutex mutex;

    //auto pm
    struct completion resume_wait;
    bool sys_suspend;
    ktime_t cmd_start_time;
    u32 suspend_d_cnt;
    u32 suspend_s_cnt;
    u32 reset_resume_cnt;
    ktime_t system_start_time;
    ktime_t suspend_start_time;
    ktime_t suspend_ttl_time;
};

typedef struct ar_usb_bus_data {
    struct urb *urb;                        /** point to the urb */
    spinlock_t urb_lock;                    /** spinlock for urb */
    atomic_t urb_state;                     /** status of the urb */
} ar_usb_bus_data_t;

/****************************
 * Function declare
 ****************************/
static void ar_usb_rx_complete(struct urb *urb);
static oal_status ar_usb_send_dnld_req(IN oal_handle *handle, u32 addr, u8* data, u32 len, u16 timeout);
static oal_status ar_usb_send_dnld_done(IN oal_handle *handle, u16 timeout);
static void ar_usb_try_recv_pend(oal_handle* handle);
static oal_status ar_usb_write_data_sync(oal_handle *handle, IN struct ar_skbreq *req);
static oal_status ar_usb_write_data_async(oal_handle *handle, IN struct ar_skbreq *req);
static oal_status ar_usb_read_data_sync(oal_handle *handle, struct ar_skbreq *req);
static oal_status ar_usb_read_data_async(oal_handle *handle, struct ar_skbreq *req);
static void ar_usb_update_card_type(oal_handle *handle, void *card);
static oal_status ar_usb_recv_packet_complete(IN oal_handle *poal_handle, IN struct ar_skbreq *req, IN oal_status status);
static oal_status ar_usb_alloc_skbreq_ex(IN oal_handle *handle, IN struct ar_skbreq *req);
/****************************
 * Local Parameters
 ****************************/
static bool g_usb_registered = false;

/** oal_callbacks */
static oal_callbacks oal_cb = {
    .oal_alloc_skbreq_ex = ar_usb_alloc_skbreq_ex,
    .oal_update_card_type = ar_usb_update_card_type,
    .oal_send_dnld_req = ar_usb_send_dnld_req,
    .oal_send_dnld_done = ar_usb_send_dnld_done,
    .oal_try_recv_pend = ar_usb_try_recv_pend,
    .oal_recv_packet_complete = ar_usb_recv_packet_complete,
    .oal_write_data_sync = ar_usb_write_data_sync,
    .oal_write_data_async = ar_usb_write_data_async,
    .oal_read_data_sync = ar_usb_read_data_sync,
    .oal_read_data_async = ar_usb_read_data_async,
};

/*-------------------USB ID list-------------------*/
static struct usb_device_id ar_usb_id_tbl[] = {
    /* support list (Vendor ID, Product ID) */
    { USB_DEVICE(ARTO_RTOS_VID, ARTO_RTOS_PID) },
    { USB_DEVICE(ARTO_ROMCODE_VID, ARTO_ROMCODE_PID) },
    { },
};

MODULE_DEVICE_TABLE(usb, ar_usb_id_tbl);

/****************************
 * Function definition
 ****************************/
/**
 * @brief This function set the status of the USB local device.
 *
 *  @param ar_dev  Handler of ar_udev.
 *  @param state   Refers to ar_usb_state.
 *
 *  @return        N/A.
 */
static void
ar_usb_set_state(struct ar_udev *udev, int state)
{
    AR_DBG(DM_USB, DL_INF, "%s: cur=%d -> new=%d\n", __func__, udev->state, state);

    udev_mutex_lock(udev);

    if (udev->state == state) {
        udev_mutex_unlock(udev);
        return;
    }

    udev->state = state;

    udev_mutex_unlock(udev);
}

/**
 * @brief This function is for skb free.
 *
 *  @param skb     The skb need to be free.
 *
 *  @return        N/A.
 */
static void
ar_usb_skb_free(struct sk_buff *skb)
{
    if (!skb)
        return;

    WARN_ON(skb->next);
    dev_kfree_skb_any(skb);
}

#if (AR_BUS_PM == 0)
int
ar_usb_autopm_get_interface(void *ar_dev)
{
    return 0;
}

void
ar_usb_autopm_put_interface(void *ar_dev)
{
}

void
ar_usb_disable_autosuspend(void *ar_dev)
{
}

void
ar_usb_enable_autosuspend(void *ar_dev)
{
}
#else
int
ar_usb_autopm_get_interface(void *ar_dev)
{
    struct ar_udev *udev = (struct ar_udev *)ar_dev;

    AR_DBG(DM_USB, DL_INF, "%s:\n", __func__);

    return usb_autopm_get_interface(udev->intf);
}

void
ar_usb_autopm_put_interface(void *ar_dev)
{
    struct ar_udev *udev = (struct ar_udev *)ar_dev;

    AR_DBG(DM_USB, DL_INF, "%s:\n", __func__);

    usb_autopm_put_interface(udev->intf);
}

void
ar_usb_disable_autosuspend(void *ar_dev)
{
    struct ar_udev *udev = (struct ar_udev *)ar_dev;

    AR_DBG(DM_USB, DL_INF, "%s: atomic=%d\n", __func__, in_atomic());

    /** 1. auto pm stats */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    udev->system_start_time.tv64 = 0;
#else
    udev->system_start_time = 0;
#endif
    /** 2. disable auto pm */
    usb_disable_autosuspend(udev->usbdev);
}

void
ar_usb_enable_autosuspend(void *ar_dev)
{
    struct ar_udev *udev = (struct ar_udev *)ar_dev;

    AR_DBG(DM_USB, DL_INF, "%s: atomic=%d\n", __func__, in_atomic());

    /** 1. auto pm stats */
    udev->system_start_time = ktime_get();

    /** 2. enable auto pm */
    usb_enable_autosuspend(udev->usbdev);
}
#endif

/**
 * @brief This function dump the information of USB.
 *
 *  @param ar_dev  Refers to ar_udev.
 *
 *  @return        N/A.
 */
void
ar_usb_dump_info(void *ar_dev)
{
    struct ar_udev *udev = (struct ar_udev *)ar_dev;
    int pktin_nopost_diff = 0;

    udev_mutex_lock(udev);

    /** auto pm info */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    if (udev->system_start_time.tv64) {
#else
    if (udev->system_start_time) {
#endif
        ktime_t currnet_time = ktime_get();
        ktime_t system_ttl_time = ktime_sub(currnet_time, udev->system_start_time);
        ktime_t tmp = udev->suspend_ttl_time;
        uint64_t a, b;

        if (udev->state == AR_USB_STATE_SLEEP) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
            ktime_t diff;
            diff = ktime_sub(currnet_time, udev->suspend_start_time);
            tmp = ktime_add(tmp, diff);
#else
            tmp += ktime_sub(currnet_time, udev->suspend_start_time);
#endif
        }

        a = (uint64_t)ktime_to_ms(tmp);
        b = (uint64_t)ktime_to_ms(system_ttl_time);

        a *= 100;
        do_div(a, b);

        AR_DBG(DM_USB, DL_ERR, "%s: usb state=%u | suspend time = %llu%%, s_cnt=%u, d_cnt=%u | reset=%u\n",
               __func__, udev->state, a, udev->suspend_s_cnt, udev->suspend_d_cnt,
               udev->reset_resume_cnt);

        //reset auto pm stats
        udev->system_start_time = currnet_time;
        #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
        udev->suspend_ttl_time.tv64 = 0;
        #else
        udev->suspend_ttl_time = 0;
        #endif
        udev->suspend_start_time = currnet_time;
        //udev->suspend_d_cnt = 0;
        //udev->suspend_s_cnt = 0;
    } else {
        AR_DBG(DM_USB, DL_INF, "%s: auto pm disabled\n", __func__);
    }

    udev_mutex_unlock(udev);
}

/**
 * @brief Process the skb size and send to ar rpc module.
 *
 *  @param poal_handle  Refers to oal_handle.
 *  @param req          skbreq that received.
 *  @param status       Refers to oal_status.
 *
 *  @return             OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_usb_alloc_skbreq_ex(IN oal_handle *handle, IN struct ar_skbreq *req)
{
    oal_status ret = OAL_STATUS_SUCCESS;
    ar_usb_bus_data_t *ar_usb_bus_data = NULL;
    gfp_t flag;

    if (!handle) {
        return OAL_STATUS_FAILURE;
    }

    if (!req) {
        return OAL_STATUS_FAILURE;
    }

    flag = (in_atomic() || irqs_disabled())? GFP_ATOMIC : GFP_KERNEL;

    ar_usb_bus_data = (void*)kzalloc(sizeof(ar_usb_bus_data_t), flag);
    if (!ar_usb_bus_data) {
        AR_DBG(DM_USB, DL_ERR, "%s: ar_usb_bus_data alloc failed!\n", __func__);
        ret = OAL_STATUS_FAILURE;
    }

    ar_usb_bus_data->urb = usb_alloc_urb(0, flag);
    if (!ar_usb_bus_data->urb) {
        AR_DBG(DM_USB, DL_ERR, "%s: usb_alloc_urb failed!\n", __func__);
        kfree(ar_usb_bus_data);
        ret = OAL_STATUS_FAILURE;
    }

    spin_lock_init(&ar_usb_bus_data->urb_lock);

    atomic_set(&ar_usb_bus_data->urb_state, URB_STATE_INITIALIZED);

    req->bus_data = ar_usb_bus_data;

    return ret;
}


/**
 * @brief This function try to pop skbreq from free list and
 * alloc urb with skb and submit to kernel.
 *
 *  @param handle  Refers to oal_handle.
 *
 *  @return        N/A.
 */
static void
ar_usb_try_recv_pend(oal_handle* handle)
{
    struct sk_buff *skb;
    int ret;
    struct ar_skbreq *req = NULL;
    gfp_t flag;
    unsigned long flags;
    struct ar_udev *udev = NULL;
    ar_usb_bus_data_t *ar_usb_bus_data = NULL;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    if (!handle) {
        AR_DBG(DM_USB, DL_WRN, "%s: handle is NULL!\n", __func__);
        return;
    }

    /* Stop recv req if handle is not up. */
    if (handle->state != AR_BUS_STATE_UP)  {
        return;
    }

    udev = (struct ar_udev *)handle->card;
    if (udev == NULL) {
        AR_DBG(DM_USB, DL_WRN, "%s: udev is NULL!\n", __func__);
        return;
    }

    flag = (in_atomic() || irqs_disabled())? GFP_ATOMIC : GFP_KERNEL;

    udev_mutex_lock(udev);

    /* Stop recv req if handle is not up. */
    if (udev->state != AR_USB_STATE_UP)  {
        udev_mutex_unlock(udev);
        return;
    }

    while ((req = oal_dequeue_skbreq_list(handle, SKBREQ_LIST_PKTIN_FREE)) != NULL)
    {
        struct urb *urb = (struct urb *)req->bus_data;

        ar_usb_bus_data = (ar_usb_bus_data_t*)req->bus_data;
        if (!ar_usb_bus_data) {
            AR_DBG(DM_USB, DL_WRN, "%s ar_usb_bus_data is NULL!\n", __FUNCTION__);
            break;
        }

        urb = (struct urb *)ar_usb_bus_data->urb;
        if (!urb) {
            AR_DBG(DM_USB, DL_WRN, "%s urb is NULL!\n", __FUNCTION__);
            /** try to alloc the urb */
            ar_usb_bus_data->urb = usb_alloc_urb(0, flag);
            oal_enqueue_skbreq_list(handle, req, SKBREQ_LIST_PKTIN_FREE);
            break;
        }

        spin_lock_irqsave(&ar_usb_bus_data->urb_lock, flags);

        // Check the status of URB
        if (atomic_read(&ar_usb_bus_data->urb_state) != URB_STATE_INITIALIZED) {
            AR_DBG(DM_USB, DL_WRN, "%s urb status is not initialized!\n", __FUNCTION__);
            atomic_set(&ar_usb_bus_data->urb_state, URB_STATE_INITIALIZED);
            oal_enqueue_skbreq_list(handle, req, SKBREQ_LIST_PKTIN_FREE);
            spin_unlock_irqrestore(&ar_usb_bus_data->urb_lock, flags);
            break;
        }

        //+1: avoid receive zero length pkt
        skb = dev_alloc_skb(handle->pktin_mtu+1);
        if (!skb) {
            AR_DBG(DM_USB, DL_WRN, "%s dev_alloc_skb failed\n", __FUNCTION__);
            oal_enqueue_skbreq_list(handle, req, SKBREQ_LIST_PKTIN_FREE);
            spin_unlock_irqrestore(&ar_usb_bus_data->urb_lock, flags);
            break;
        }

        req->skb = skb;

        oal_enqueue_skbreq_list(handle, req, SKBREQ_LIST_PKTIN_PEND);

        /** Set status of urb as submitted */
        atomic_set(&ar_usb_bus_data->urb_state, URB_STATE_SUBMITTED);

        if (is_message_type(req->type)) {
#if AR_MSG_EP_TYPE == AR_USB_EPTYPE_BULK
            usb_fill_bulk_urb(urb, udev->usbdev,
                            usb_rcvbulkpipe(udev->usbdev, AR_USB_EP_RX),
                            req->skb->data, skb_tailroom(req->skb),
                            ar_usb_rx_complete, req);
#else
            usb_fill_int_urb(urb, udev->usbdev,
                        usb_rcvintpipe(udev->usbdev, AR_USB_EP_RX),
                        req->skb->data, skb_tailroom(req->skb),
                        ar_usb_rx_complete, req, 1);
#endif
        } else {
            usb_fill_bulk_urb(urb, udev->usbdev,
                            usb_rcvbulkpipe(udev->usbdev, AR_USB_EP_RX),
                            req->skb->data, skb_tailroom(req->skb),
                            ar_usb_rx_complete, req);
        }

        ret = usb_submit_urb(urb, flag);
        if (ret) {
            AR_DBG(DM_USB, DL_WRN, "%s: usb_submit_urb failed (ret %d)!\n", __func__, ret);
            oal_skbreq_deinit(handle, req, true, &handle->pktin_pendcount);
            oal_enqueue_skbreq_list(handle, req, SKBREQ_LIST_PKTIN_FREE);
            spin_unlock_irqrestore(&ar_usb_bus_data->urb_lock, flags);
            udev_mutex_unlock(udev);
            return;
        }
        usb_mark_last_busy(urb->dev);
        spin_unlock_irqrestore(&ar_usb_bus_data->urb_lock, flags);
    }

    udev_mutex_unlock(udev);

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);
}

/**
 * @brief Process the skb size and send to ar rpc module.
 *
 *  @param poal_handle  Refers to oal_handle.
 *  @param req          skbreq that received.
 *  @param status       Refers to oal_status.
 *
 *  @return             OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_usb_recv_packet_complete(IN oal_handle *poal_handle,
                                      IN struct ar_skbreq *req,
                                      IN oal_status status)
{
    struct sk_buff *rx_skb;
    oal_status ret = OAL_STATUS_SUCCESS;
    struct urb *urb = NULL;
    unsigned long flags;
    ar_usb_bus_data_t *ar_usb_bus_data = NULL;

    if (!poal_handle) {
        return OAL_STATUS_FAILURE;
    }

    if (!req) {
        ret = OAL_STATUS_FAILURE;
        goto fail;
    }

    ar_usb_bus_data = (ar_usb_bus_data_t*)req->bus_data;
    if (!ar_usb_bus_data) {
        AR_DBG(DM_USB, DL_ERR, "%s ar_usb_bus_data is NULL!\n", __FUNCTION__);
        ret = OAL_STATUS_FAILURE;
        goto fail;
    }

    urb = (struct urb *)ar_usb_bus_data->urb;
    if (!urb) {
        AR_DBG(DM_USB, DL_ERR, "%s urb is NULL!\n", __FUNCTION__);
        ret = OAL_STATUS_FAILURE;
        goto fail;
    }

    AR_DBG(DM_USB, DL_VRB, "%s: status=%d, actual_length=%d\n", __func__, urb->status, urb->actual_length);

    spin_lock_irqsave(&ar_usb_bus_data->urb_lock, flags);

    // Check the status of URB
    if (atomic_read(&ar_usb_bus_data->urb_state) != URB_STATE_SUBMITTED) {
        AR_DBG(DM_USB, DL_INF, "%s urb status is not submitted!\n", __FUNCTION__);
        ret = OAL_STATUS_FAILURE;
    }
    else if ((urb->status != 0) || (!urb->actual_length)) {
        if (poal_handle->state == AR_BUS_STATE_SLEEP) {
            AR_DBG(DM_USB, DL_INF, "%s: Error! status=%d, actual_length=%d, dev state=%u\n", __func__,
                   urb->status, urb->actual_length, poal_handle->state);
        } else {
            AR_DBG(DM_USB, DL_VRB, "%s: Error! status=%d, actual_length=%d, dev state=%u\n", __func__,
                   urb->status, urb->actual_length, poal_handle->state);
        }

        ret = OAL_STATUS_FAILURE;
    }
    else if ((poal_handle->state != AR_BUS_STATE_UP) && (poal_handle->state != AR_BUS_STATE_SLEEP)) {
        AR_DBG(DM_USB, DL_VRB, "%s: Error! dev state=%d", __func__, poal_handle->state);
        ret = OAL_STATUS_FAILURE;
    }
    spin_unlock_irqrestore(&ar_usb_bus_data->urb_lock, flags);

    if (ret == OAL_STATUS_SUCCESS) {
        if (poal_handle->state == AR_BUS_STATE_SLEEP) {
            AR_DBG(DM_USB, DL_VRB, "%s: dev state=%d, msg len=%d\n", __func__, poal_handle->state, urb->actual_length);
        } else {
            AR_DBG(DM_USB, DL_VRB, "%s: dev state=%d, msg len=%d\n", __func__, poal_handle->state, urb->actual_length);
        }

        if (req->skb) {
            /* data has been put to the skb through urb */
            skb_put(req->skb, urb->actual_length);

            if (poal_handle) {
                ar_rpc_rx_pkt(poal_handle, req->skb);
            }
            else {
                /* Free the skb */
                dev_kfree_skb_any(req->skb);
                req->skb = NULL;
            }
        }

        spin_lock_irqsave(&ar_usb_bus_data->urb_lock, flags);

        /** Leave skb for rpc/net_dev */
        req->skb = NULL;
        oal_enqueue_skbreq_list(poal_handle, req, SKBREQ_LIST_PKTIN_FREE);
        atomic_set(&ar_usb_bus_data->urb_state, URB_STATE_INITIALIZED);
        spin_unlock_irqrestore(&ar_usb_bus_data->urb_lock, flags);

        if (poal_handle->state != AR_BUS_STATE_SLEEP) {
            /** alloc new skb and submit urb to kernel */
            if (poal_handle->callbacks.oal_try_recv_pend) {
                poal_handle->callbacks.oal_try_recv_pend(poal_handle);
            }
        }
    } else {
        AR_DBG(DM_USB, DL_VRB, "%s: Error! dev state=%d, urb status=%d, actual_length=%d\n", __func__,
                 poal_handle->state, urb->status, urb->actual_length);

        spin_lock_irqsave(&ar_usb_bus_data->urb_lock, flags);

        /* Free the skb */
        if (req->skb) {
            dev_kfree_skb_any(req->skb);
            req->skb = NULL;
        }

        oal_enqueue_skbreq_list(poal_handle, req, SKBREQ_LIST_PKTIN_FREE);
        if (atomic_read(&ar_usb_bus_data->urb_state) != URB_STATE_KILLING) {
            atomic_set(&ar_usb_bus_data->urb_state, URB_STATE_INITIALIZED);
        }
        spin_unlock_irqrestore(&ar_usb_bus_data->urb_lock, flags);
    }

    return OAL_STATUS_SUCCESS;

fail:
    return ret;
}

/**
 * @brief Kill/Free the urbs have been submit to kernel.
 *
 *  @param handle  Refers to oal_handle.
 *  @param q        The list contains the skbreqs.
 *  @param cancel   Kill the urb if true, otherwise free the urb.
 *  @param skb_free Whether free the skb in skbreq.
 *
 *  @return        N/A.
 */
static void
ar_usb_kill_urbs(oal_handle *handle, struct list_head *q, bool cancel, bool skb_free)
{
    struct ar_skbreq *req, *next;
    struct urb *urb = NULL;
    int i = 0;
    unsigned long flags;
    ar_usb_bus_data_t *ar_usb_bus_data = NULL;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    AR_DBG(DM_USB, DL_WRN, "%s: q=0x%p, cancel=%d, skb_free=%d\n",
           __func__, q, cancel, skb_free);

    list_for_each_entry_safe(req, next, q, list) {
        ar_usb_bus_data = (ar_usb_bus_data_t*)req->bus_data;
        if (!ar_usb_bus_data) {
            AR_DBG(DM_USB, DL_ERR, "%s bad ar_usb_bus_data!\n", __FUNCTION__);
            break;
        }

        urb = (struct urb *)ar_usb_bus_data->urb;
        if (!urb) {
            AR_DBG(DM_USB, DL_ERR, "%s: bad req\n", __func__);
            break;
        }

        i++;

        if (cancel) {
            // Check the status and change
            if (atomic_cmpxchg(&ar_usb_bus_data->urb_state, URB_STATE_SUBMITTED, URB_STATE_KILLING)) {
                usb_kill_urb(urb);
                usb_unlink_urb(urb);
            }
            else {
                AR_DBG(DM_USB, DL_WRN, "%s: bad req not submitted\n", __func__);
            }
        } else {
            if (atomic_cmpxchg(&ar_usb_bus_data->urb_state, URB_STATE_KILLING, URB_STATE_KILLED) ||
                atomic_cmpxchg(&ar_usb_bus_data->urb_state, URB_STATE_INITIALIZED, URB_STATE_KILLED)) {
                // free urb
                usb_free_urb(urb);
            }
            else {
                AR_DBG(DM_USB, DL_WRN, "%s: bad req not killing or initialized\n", __func__);
            }

            // free skb
            if (skb_free && req->skb) {
                ar_usb_skb_free(req->skb);
                req->skb = NULL;
            }
        }
    }

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);
}

/**
 * @brief Cancel the urbs have been submit to kernel.
 *
 *  @param udev    Local structure if usb device.
 *
 *  @return        N/A.
 */
static void
ar_usb_cancel_all_urbs(struct ar_udev *udev)
{
    oal_handle *handle = NULL;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    handle = (oal_handle*)(udev->handle);
    if (!handle) {
        AR_DBG(DM_USB, DL_ERR, "%s: handle is NULL!\n", __func__);
        return;
    }

    // usb_kill_anchored_urbs(&udev->msgout_anchor);

    /** No need to kill urbs in sync mode */
#if 0
    ar_usb_kill_urbs(handle, &handle->pktout_postq, true, false);
    ar_usb_kill_urbs(handle, &handle->msgout_postq, true, false);
#endif
    ar_usb_kill_urbs(handle, &handle->pktin_pendq, true, false);

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);
}

/**
 * @brief Init the state of the USB.
 *
 *  @param udev    Local structure if usb device.
 *
 *  @return        0 for success, otherwise fail.
 */
static int
ar_usb_up(struct ar_udev *udev)
{
    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    udev_mutex_lock(udev);

    if (udev->state == AR_USB_STATE_UP) {
        udev_mutex_unlock(udev);
        return 0;
    }

    udev_mutex_unlock(udev);

    //1. resource allocate
    // init_usb_anchor(&udev->msgout_anchor);

    //2. state update
    ar_usb_set_state(udev, AR_USB_STATE_UP);

    return 0;
}

/**
 * @brief Cancel all urbs and deinit the state of the USB.
 *
 *  @param udev    Local structure if usb device.
 *
 *  @return        N/A.
 */
static void
ar_usb_down(struct ar_udev *udev)
{
    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    if (udev == NULL) {
        AR_DBG(DM_USB, DL_WRN, "%s: udev is null\n", __func__);
        return;
    }

    udev_mutex_lock(udev);

    if (udev->state == AR_USB_STATE_DOWN) {
        AR_DBG(DM_USB, DL_ERR, "%s: quit by udev state is %u\n", __func__, udev->state);
        udev_mutex_unlock(udev);
        return;
    }

    udev_mutex_unlock(udev);

    ar_usb_set_state(udev, AR_USB_STATE_DOWN);

    ar_usb_cancel_all_urbs(udev);

    //urb statistic
    //todo : check all return
    // AR_DBG(DM_USB, DL_MSG, "%s: pktout_free=%d | pktin_free=%d, pktin_pend=%d | msgin_free=%d, msgout=%d\n",
    //        __func__, udev->pktout_freecount, udev->pktin_freecount, udev->pktin_pendcount,
    //        udev->msgin_freecount, atomic_read(&(udev->msgout_cnt)));
}

/**
 * @brief Kill all urbs in skbreq list. Wait until tx/rx done or timeout
 *      before kill the urb of skbreq.
 *
 *  @param udev    Local structure if usb device.
 *
 *  @return        N/A.
 */
static void
ar_usb_dettach(struct ar_udev *udev)
{
    oal_handle *handle = NULL;
    int loop_cnt = 0, urbs_back = 0;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    AR_DBG(DM_USB, DL_WRN, "%s: udev=0x%p\n", __func__, udev);

    handle = (oal_handle*)(udev->handle);
    if (!handle) {
        AR_DBG(DM_USB, DL_ERR, "%s: handle is NULL!\n", __func__);
        return;
    }

    //1. free urb/queue
    loop_cnt = 0;
    do {
        loop_cnt++;

        if (handle->pktin_freecount + handle->pktin_pendcount == OAL_PKTIN_NUM) {
            urbs_back = 1;
            ar_usb_kill_urbs(handle, &handle->pktin_freeq, false, false);
            ar_usb_kill_urbs(handle, &handle->pktin_pendq, false, false);
        } else {
            urbs_back = 0;
            AR_DBG(DM_USB, DL_WRN, "%s: wait pktin urb (pend=%d, free=%d, post=%d)\n",
                    __func__, handle->pktin_pendcount, handle->pktin_freecount, handle->pktin_postcount);
            msleep(20);
        }
    } while((urbs_back == 0) && (loop_cnt < 10));

    /** No need to kill urbs in sync mode */
    // loop_cnt = 0;
    // do {
    //     loop_cnt++;

    //     if (handle->pktout_freecount == OAL_PTKOUT_NUM) {
    //         urbs_back = 1;
    //         ar_usb_kill_urbs(handle, &handle->pktout_freeq, false, false);
    //     } else {
    //         urbs_back = 0;
    //         AR_DBG(DM_USB, DL_WRN, "%s: wait pktout urb (cur=%d)\n", __func__, handle->pktout_freecount);
    //         msleep(20);
    //     }
    // } while((urbs_back == 0) && (loop_cnt < 10));

    // loop_cnt = 0;
    // do {
    //     loop_cnt++;

    //     if (handle->msgout_freecount == OAL_MSGOUT_NUM) {
    //         urbs_back = 1;
    //         ar_usb_kill_urbs(handle, &handle->msgout_freeq, false, false);
    //     } else {
    //         urbs_back = 0;
    //         AR_DBG(DM_USB, DL_WRN, "%s: wait msgout urb (cur=%d)\n", __func__, handle->msgout_freecount);
    //         msleep(20);
    //     }
    // } while((urbs_back == 0) && (loop_cnt < 10));
}

/**
 * @brief Init the control flag of the usb.
 *
 *  @param udev    Local structure if usb device.
 *
 *  @return        0 for success, otherwise fail.
 */
static int
ar_usb_attach(struct ar_udev *udev)
{
    // TBD:
    char *disable_auto_pm = NULL;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    AR_DBG(DM_USB, DL_WRN, "%s: pm build=%u, enabled=%s(%u)\n",
           __func__, AR_BUS_PM, ((disable_auto_pm == NULL) && (autopm_on))? "YES":"NO", autopm_on);

    //1. status, sync
    ar_usb_set_state(udev, AR_USB_STATE_DOWN);

    if (disable_auto_pm || (autopm_on == 0)) {
        ar_misc_ctrl |= AUTOPM_DISABLE;
    } else {
        ar_misc_ctrl &= ~(AUTOPM_DISABLE);
    }

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);

    return 0;
}

/**
 * @brief Try to up the usb and roll back if fail.
 *
 *  @param udev    Local structure if usb device.
 *
 *  @return        0 for success, otherwise fail.
 */
static int
ar_usb_bus_setup(struct ar_udev *udev)
{
    int ret;
    // unsigned long __attribute__((__unused__)) left_time = 0;

    //1. enable usb data i/o
    ret = ar_usb_up(udev);
    if (ret) {
        //because usb state not up
        AR_DBG(DM_USB, DL_ERR, "%s: ar_usb_up failed(%d)\n", __func__, ret);
        goto fail;
    }

    return 0;

fail:
    ar_usb_down(udev);

    return ret;
}


/**
 * @brief Do extra work of probe(attach and up the usb device).
 *
 *  @param udev    Local structure if usb device.
 *
 *  @return        0 for success, otherwise fail.
 */
static int
ar_usb_probe_ex(struct ar_udev *udev)
{
    int ret;
    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    //1. usb resource preparation
    ret = ar_usb_attach(udev);
    if (ret) {
        AR_ERROR(URB_INIT_FAIL);
        AR_DBG(DM_USB, DL_ERR, AR_LOG"%s: usb attach failed (%d)\n", __func__, ret);
        return -ENODEV;
    }

    //2. establish host/target communication
    ret = ar_usb_bus_setup(udev);
    if (ret) {
        AR_DBG(DM_USB, DL_ERR, AR_LOG"%s: establish communication failed (dev=%s, err=%d)\n", __func__, dev_name(udev->dev), ret);
        goto fail;
    }

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);

    return 0;

fail:
    //release resource
    ar_usb_dettach(udev);

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);

    return ret;
}

/**
 * @brief Disconnect usb device(down and dettach the usb device).
 *
 *  @param udev    Local structure if usb device.
 *
 *  @return        N/A
 */
static void
ar_usb_disconnect_ex(struct ar_udev *udev)
{
    AR_DBG(DM_USB, DL_WRN, "%s: udev=0x%p\n", __func__, udev);

    if (!udev)
        return;

    ar_usb_down(udev);
    ar_usb_dettach(udev);
    ar_usb_dump_info(udev);
}

/**
 * @brief Strandard probe function of usb device.
 *
 *  @param intf    Refers to struct usb_interface.
 *  @param id      Refers to struct usb_device_id.
 *
 *  @return        0 for success, otherwise fail.
 */
static int
ar_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    oal_handle *handle = NULL;
     struct usb_device *usb = interface_to_usbdev(intf);
    struct ar_udev *udev;
    struct usb_interface_descriptor    *desc;
    struct usb_endpoint_descriptor *endpoint;
    oal_card_mode mode;
    int ret = 0;
    u8 endpoint_num, ep;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    AR_DBG(DM_USB, DL_MSG, AR_LOG"%s: VID=0x%04x, PID=0x%04x, dev class=0x%x, cfg num=%d\n", __func__,
           id->idVendor, id->idProduct,
           usb->descriptor.bDeviceClass, usb->descriptor.bNumConfigurations);

    //1. device info preparation
    udev = kzalloc(sizeof(*udev), GFP_ATOMIC);
    if (udev == NULL) {
        AR_ERROR(UDEV_ALLOC_FAIL);
        return -ENOMEM;
    }

    /** Init the mutex */
    udev_mutex_init(udev);

    udev_mutex_lock(udev);

    udev->usbdev = usb;
    udev->dev = &usb->dev;
    udev->intf = intf;

    //protect whole probe phase
    // init_completion(&udev->dev_init_lock);

    /** usb autopm*/
    init_completion(&udev->resume_wait);
    udev->sys_suspend = false;
    udev->suspend_d_cnt = 0;
    udev->suspend_s_cnt = 0;
    udev->reset_resume_cnt = 0;
    #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    udev->cmd_start_time.tv64 = 0;
    udev->system_start_time.tv64 = 0;
    udev->suspend_start_time.tv64 = 0;
    udev->suspend_ttl_time.tv64 = 0;
    #else
    udev->cmd_start_time = 0;
    udev->system_start_time = 0;
    udev->suspend_start_time = 0;
    udev->suspend_ttl_time = 0;
    #endif

    /** Save the local structure */
    usb_set_intfdata(intf, udev);

    udev_mutex_unlock(udev);

    intf->needs_remote_wakeup = 1;
    pm_runtime_set_autosuspend_delay(udev->dev, AR_USB_AUTOPM_DELAY);

    AR_DBG(DM_USB, DL_VRB, "%s: can_wakeup(usb config desc)=%u, needs_remote_wakeup=%u",
          __func__, usb->dev.power.can_wakeup, intf->needs_remote_wakeup);

    //2. check device enumeration result
    desc = &intf->altsetting[0].desc;
    AR_DBG(DM_USB, DL_MSG, AR_LOG"%s: interface num=%d, class=0x%x, subclass=0x%x, protocol=0x%x, ep num=%d\n",
             __func__, desc->bInterfaceNumber, desc->bInterfaceClass, desc->bInterfaceSubClass,
             desc->bInterfaceProtocol, desc->bNumEndpoints);

    if (id->idVendor == ARTO_RTOS_VID) {
        //check endpoint number
        if ((desc->bNumEndpoints != AR_USB_NUM_OF_EP) && (desc->bNumEndpoints != (AR_USB_NUM_OF_EP-1)) && (desc->bNumEndpoints != (AR_USB_NUM_OF_EP-3))) {
            AR_DBG(DM_USB, DL_ERR, AR_LOG"%s: endpoint number(%u) mismatch!\n", __func__, desc->bNumEndpoints);
            ret = -ENODEV;
            AR_ERROR(USB_EP_NUM_ERROR);
            goto fail;
        }

        //check endpoint type/dir/addr
        for (ep = 0; ep < desc->bNumEndpoints; ep++) {
            endpoint = &intf->altsetting[0].endpoint[ep].desc;
            endpoint_num = usb_endpoint_num(endpoint);

            AR_DBG(DM_USB, DL_MSG, AR_LOG"%s: ep[%d] num=%d, type=%d, dir=%s\n", __func__, ep, endpoint_num,
                    usb_endpoint_type(endpoint), usb_endpoint_dir_in(endpoint)? "IN":"OUT");
        }
    }

    //usb speed
       if (usb->speed == USB_SPEED_SUPER_PLUS)
        AR_DBG(DM_USB, DL_MSG, AR_LOG"%s: USB3.1 detected\n", __func__);
    else if (usb->speed == USB_SPEED_SUPER)
        AR_DBG(DM_USB, DL_MSG, AR_LOG"%s: USB3.0 detected\n", __func__);
    else if (usb->speed == USB_SPEED_HIGH)
        AR_DBG(DM_USB, DL_MSG, AR_LOG"%s: USB2.0 detected\n", __func__);
    else
        AR_DBG(DM_USB, DL_MSG, AR_LOG"%s: USB device speed = %d\n", __func__, usb->speed);

    //3. establish the communication with device
    ret = ar_usb_probe_ex(udev);
    if (ret) {
        goto fail;
    }

      //4. device initialize success
#if 0   //autopm on/off by proc
    ar_usb_enable_autosuspend(udev);
#endif
    // complete_all(&udev->dev_init_lock);

    /*5. Init oal */
    if (id->idVendor == ARTO_ROMCODE_VID) {
        mode = OAL_CARD_MODE_FW_DNLD;
    }
    else if (id->idVendor == ARTO_RTOS_VID) {
        mode = OAL_CARD_MODE_RPC;
    }
    else {
        ret = -ENODEV;
        goto fail;
    }

    handle = oal_add_card((void*)udev, udev->dev, mode, &oal_cb);
    if (handle == NULL) {
        AR_DBG(DM_GENERIC, DL_ERR, "oal_add_card failed!\n");
        goto fail;
    }

    /* Submit usb urb */
    if (handle->callbacks.oal_try_recv_pend) {
        handle->callbacks.oal_try_recv_pend(handle);
    }

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);

    return 0;

fail:
    ar_usb_disconnect_ex(udev);
    //set NULL to avoid free udev in disconnect handler
    usb_set_intfdata(intf, NULL);
       // complete_all(&udev->dev_init_lock);
    kfree(udev);

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);

    return ret;
}

/**
 * @brief Standard disconnect function of usb device.
 *
 *  @param intf    Refers to struct usb_interface.
 *
 *  @return        N/A
 */
static void
ar_usb_disconnect(struct usb_interface *intf)
{
    struct ar_udev *udev;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    udev = (struct ar_udev *)usb_get_intfdata(intf);
    if (udev) {
        // wait_for_completion(&udev->dev_init_lock);
        //check interface data again for probe failed case
        if (!usb_get_intfdata(intf))
            goto done;

        ar_usb_disconnect_ex(udev);

        oal_remove_card(udev);

        usb_set_intfdata(intf, NULL);

        kfree(udev);
    }

done:
    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);
}

/**
 * @brief Read data as sync mode and call complete func after data received.
 *        Rgegister as API oal_read_data_sync.
 *
 *  @param handle  Refers to oal_handle.
 *  @param req     skreq in list includes skb and urb.
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_usb_read_data_sync(oal_handle *handle, struct ar_skbreq *req)
{
    oal_status ret = OAL_STATUS_SUCCESS;
    struct ar_udev *card = NULL;
    u8 ep_id = AR_USB_EP_TX;
    int actual_length = 0;
    int err;

    /* TBD:NOT DEVELOPED */

    if (handle == NULL) {
        AR_DBG(DM_USB, DL_ERR, "%s: handle is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    card = (struct ar_udev *)handle->card;
    if (card == NULL) {
        AR_DBG(DM_USB, DL_ERR, "%s: card is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    skb_trim(req->skb, 0);
    err = usb_bulk_msg(card->usbdev,                                    //dev
                       usb_rcvbulkpipe(card->usbdev, ep_id),            //pipe
                       req->skb->data,                                  //data
                       skb_tailroom(req->skb),                          //len
                       &req->actual_length,                             //actual length
                       req->timeout);                                   //timeout

    if (err) {
        AR_DBG(DM_USB, DL_ERR, "%s: usb_bulk_msg FAILED\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    } else {
        skb_put(req->skb, req->actual_length);
    }

    if (is_packet_type(req->type)) {
        if (handle->callbacks.oal_recv_packet_complete) {
            handle->callbacks.oal_recv_packet_complete(handle, req, ret);
        }
    }

exit:
    return ret;
}

/**
 * @brief Callback of the submitted rx urb. Remove the skbreq from pending list
 *        and push into post list, and then start the main_workqueue.
 *
 *  @param urb     Rx URB that had been submitted.
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static void
ar_usb_rx_complete(struct urb *urb)
{
    struct ar_skbreq *req = NULL;
    oal_handle *handle = NULL;
    struct ar_udev *udev = NULL;
    ar_usb_bus_data_t *ar_usb_bus_data = NULL;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    if (!urb) {
        AR_DBG(DM_USB, DL_ERR, "%s urb is NULL\n", __FUNCTION__);
        return;
    }

    req = (struct ar_skbreq *)urb->context;
    handle = req->handle;

    if (!handle) {
        AR_DBG(DM_USB, DL_ERR, "%s handle is NULL\n", __FUNCTION__);
        return;
    }

    udev = (struct ar_udev *)handle->card;
    if (udev == NULL) {
        AR_DBG(DM_USB, DL_WRN, "%s: udev is NULL!\n", __func__);
        return;
    }

    if (!req) {
        AR_DBG(DM_USB, DL_ERR, "%s req is NULL\n", __FUNCTION__);
        return;
    }

    ar_usb_bus_data = (ar_usb_bus_data_t*)req->bus_data;
    if (!ar_usb_bus_data) {
        AR_DBG(DM_USB, DL_ERR, "%s ar_usb_bus_data is NULL!\n", __FUNCTION__);
        return;
    }

    if ((udev->state != AR_USB_STATE_UP) ||
        (atomic_read(&ar_usb_bus_data->urb_state) == URB_STATE_KILLING)) {
        if (req->skb) {
            dev_kfree_skb_any(req->skb);
            req->skb = NULL;
        }
        goto done;
    }

    /** Remove skbreq from pending list */
    oal_skbreq_deinit(handle, req, false, &handle->pktin_pendcount);

    switch (urb->status) {
        case 0:
        {
            /** Push skbreq to post list and waiting for pop by rx workqueue */
            oal_enqueue_skbreq_list(handle, req, SKBREQ_LIST_PKTIN_POST);

            AR_DBG(DM_GENERIC, DL_INF, "%s: trigger main_workqueue %p\n", __func__, handle->main_workqueue);

            /* trigger main workqueue */
            oal_queue_work(handle, handle->main_workqueue);
            break;
        }
        case -ECONNRESET:
        case -ESHUTDOWN:
        default:
        {
            if (req->skb) {
                dev_kfree_skb_any(req->skb);
                req->skb = NULL;
            }

            /** Push skbreq to free list */
            oal_enqueue_skbreq_list(handle, req, SKBREQ_LIST_PKTIN_FREE);

            break;
        }
    }
done:
    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);
}

/**
 * @brief Read data as async mode. Rgegister as API oal_read_data_async.
 *
 *  @param handle  Refers to oal_handle.
 *  @param req     skreq in list includes skb and urb.
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_usb_read_data_async(oal_handle *handle, struct ar_skbreq *req)
{
    oal_status ret = OAL_STATUS_SUCCESS;

#if 0/** TBD*/
    struct ar_udev *card = NULL;
    struct sk_buff *skb;
    u8 ep_id = AR_USB_EP_RX/*req->type*/;
    int actual_length = 0;
    int err;
    gfp_t flag;
    struct urb *urb = NULL;

    if (handle == NULL) {
        AR_DBG(DM_USB, DL_ERR, "%s: handle is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    card = (struct ar_udev *)handle->card;
    if (card == NULL) {
        AR_DBG(DM_USB, DL_ERR, "%s: card is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    urb = (struct urb *)req->bus_data;
    if (!urb) {
        AR_DBG(DM_USB, DL_ERR, "%s: bad req\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    //+1: avoid receive zero length pkt
    skb = dev_alloc_skb(handle->pktin_mtu+1);
    if (!skb) {
        AR_DBG(DM_USB, DL_ERR, "%s dev_alloc_skb failed\n", __FUNCTION__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    req->skb = skb;

    if (is_message_type(req->type)) {
#if AR_MSG_EP_TYPE == AR_USB_EPTYPE_BULK
        usb_fill_bulk_urb(urb, card->usbdev,
                        usb_rcvbulkpipe(card->usbdev, ep_id),
                        req->skb->data, skb_tailroom(req->skb),
                        ar_usb_rx_complete, req);
#else
        usb_fill_int_urb(urb, card->usbdev,
                     usb_rcvintpipe(card->usbdev, ep_id),
                     req->skb->data, skb_tailroom(req->skb),
                     ar_usb_rx_complete, req, 1);
#endif
    } else {
        usb_fill_bulk_urb(urb, card->usbdev,
                        usb_rcvbulkpipe(card->usbdev, ep_id),
                        req->skb->data, skb_tailroom(req->skb),
                        ar_usb_rx_complete, req);
    }

    flag = (in_atomic() || irqs_disabled())? GFP_ATOMIC : GFP_KERNEL;

    err = usb_submit_urb(urb, flag);
    if (err) {
        AR_DBG(DM_USB, DL_ERR, "%s: usb_submit_urb FAILED\n", __func__);
        ret = OAL_STATUS_FAILURE;

        if (is_packet_type(req->type)) {
            if (poal_handle->callbacks.oal_recv_packet_complete) {
                poal_handle->callbacks.oal_recv_packet_complete(handle, req, ret);
            }
        }
    }

exit:
#endif
    return ret;
}

/**
 * @brief Write data as sync mode and call complete func after data sent.
 *        Rgegister as API ar_usb_write_data_sync.
 *
 *  @param handle  Refers to oal_handle.
 *  @param req     skreq in list includes skb and urb.
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_usb_write_data_sync(oal_handle *handle, IN struct ar_skbreq *req)
{
    oal_status ret = OAL_STATUS_SUCCESS;
    struct ar_udev *card = NULL;
    u8 ep_id = AR_USB_EP_TX/*req->type*/;
    int err;
    u32 actual_length;

    AR_DBG(DM_USB, DL_VRB, AR_FN_ENTRY_STR);

    if (handle == NULL) {
        AR_DBG(DM_USB, DL_ERR, "%s: handle is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    card = (struct ar_udev *)handle->card;
    if (card == NULL) {
        AR_DBG(DM_USB, DL_ERR, "%s: card is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    if (req->skb == NULL) {
        AR_DBG(DM_USB, DL_ERR, "%s: skb is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto free_req;
    }

    err = usb_bulk_msg(card->usbdev,                                    //dev
                       usb_sndbulkpipe(card->usbdev, ep_id),            //pipe
                       req->skb->data,                                  //data
                       req->skb->len,                                   //len
                       &req->actual_length,                             //actual length
                       req->timeout);                                   //timeout

    if (err && (err != -ENODEV)) {
        AR_DBG(DM_USB, DL_ERR, "%s: usb_bulk_msg FAILED err %d\n", __func__, err);
        ret = OAL_STATUS_FAILURE;
    }

    if ((req->skb->len % 512) == 0) {
        err = usb_bulk_msg(card->usbdev,                                    //dev
                           usb_sndbulkpipe(card->usbdev, ep_id),            //pipe
                           NULL,                                            //data
                           0,                                               //len
                           &actual_length,                                  //actual length
                           2000);                                           //timeout

        //check result
        if (err < 0) {
            AR_DBG(DM_USB, DL_ERR, "%s: send zero pkt failed (err=%d), len=%d\n", __func__, err, actual_length);
            ret = OAL_STATUS_FAILURE;
        } else {
           AR_DBG(DM_USB, DL_INF, "%s: send zero pkt completed, len=%d\n", __func__, actual_length);
        }
    }

free_req:
    if (is_message_type(req->type)) {
        oal_send_msg_complete(handle, req, ret);
    }
    else if (is_packet_type(req->type)) {
        oal_send_packet_complete(handle, req, ret);
    }

exit:
    AR_DBG(DM_USB, DL_VRB, AR_FN_LEAVE_STR);
    return ret;
}

/**
 * @brief Callback of the submitted tx urb. Call oal_send_msg_complete and
 *        oal_send_packet_complete to process the different type of skbreq.
 *
 *  @param urb     Rx URB that had been submitted .
 *
 *  @return        N/A
 */
static void oal_usb_tx_complete(struct urb *urb)
{
    struct ar_skbreq *req = (struct ar_skbreq *)urb->context;
    oal_handle *handle = req->handle;

    if (is_message_type(req->type)) {
        oal_send_msg_complete(handle, req, OAL_STATUS_SUCCESS);
    }

    if (is_packet_type(req->type)) {
        oal_send_packet_complete(handle, req, OAL_STATUS_SUCCESS);
    }
}

/**
 * @brief Write data as async mode. Rgegister as API ar_usb_write_data_async.
 *
 *  @param handle  Refers to oal_handle.
 *  @param req     skreq in list includes skb and urb.
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_usb_write_data_async(oal_handle *handle, IN struct ar_skbreq *req)
{
    oal_status ret = OAL_STATUS_SUCCESS;
    struct ar_udev *card = NULL;
    u8 ep_id = AR_USB_EP_TX;
    gfp_t flag;
    int status;
    struct urb *urb = NULL;

    if (handle == NULL) {
        AR_DBG(DM_USB, DL_ERR, "%s: handle is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    card = (struct ar_udev *)handle->card;
    if (card == NULL) {
        AR_DBG(DM_USB, DL_ERR, "%s: card is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    if (!req) {
        AR_DBG(DM_USB, DL_ERR, "%s: req is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    if (!req->skb) {
        AR_DBG(DM_USB, DL_ERR, "%s: skb is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    urb = (struct urb *)req->bus_data;
    if (!urb) {
        AR_DBG(DM_USB, DL_ERR, "%s: bad req\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    usb_fill_bulk_urb(urb, card->usbdev,
                        usb_sndbulkpipe(card->usbdev, ep_id),
                        req->skb->data, req->skb->len,
                        oal_usb_tx_complete, req);

    flag = (in_atomic() || irqs_disabled())? GFP_ATOMIC : GFP_KERNEL;

    status = usb_submit_urb(urb, flag);
    if (status) {
        AR_DBG(DM_USB, DL_ERR, "%s: usb_submit_urb FAILED\n", __func__);

        ret = OAL_STATUS_FAILURE;
        if (is_message_type(req->type)) {
            oal_send_msg_complete(handle, req, ret);
        }

        if (is_packet_type(req->type)) {
            oal_send_packet_complete(handle, req, ret);
        }
    }

exit:
    return ret;
}

/**
 *  @brief  This function sends usb control msg
 *
 *  @param handle       A pointer to driver_handle
 *  @param request      USB request cmd
 *  @param value        msg value
 *  @param index        msg index
 *  @param data         msg data
 *  @param size         msg size
 *  @param timeout      Timeout in unit ms
 *
 *  @return             error code
 */
static int
usb_send_control_msg(IN struct usb_device *udev, IN u16 request, IN u32 value,
    IN u16 index, IN u8* data, IN int size, int timeout)
{
    int status = -ENODEV;

    if (!udev) {
        return -ENODEV;
    }

    /* cp must be memory that has been allocated by kmalloc */
    status = usb_control_msg(udev,
            usb_sndctrlpipe(udev, 0),
            request,
            USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
            value, index, data, size, timeout);

    status = min(status, 0);

    if (status < 0) {
        AR_DBG(DM_USB, DL_ERR, "%s() Failed sending control message, error %d.\n",__FUNCTION__, status);
    }

    return status;
}

/**
 * @brief Write firmware download req. Rgegister as API ar_usb_send_dnld_req.
 *
 *  @param handle  Refers to oal_handle.
 *  @param addr    Address to upload the firmware data
 *  @param data    Firmware data to sent
 *  @param len     Firmware data len to sent
 *  @param timeout Timeout in unit ms
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_usb_send_dnld_req(IN oal_handle *handle, u32 addr, u8* data, u32 len, u16 timeout)
{
    oal_status status = OAL_STATUS_SUCCESS;
    int i, ret, tmp = 0;
    struct artousb_cmd_header* cmd = NULL;
    unsigned char* data_buf = NULL;
    unsigned char *tmp_buffer = NULL;
    struct ar_udev *udev = NULL;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    if (!handle) {
        AR_DBG(DM_USB, DL_WRN, "%s: handle is NULL!\n", __func__);
        return OAL_STATUS_FAILURE;
    }

    udev = (struct ar_udev *)handle->card;
    if (udev == NULL) {
        AR_DBG(DM_USB, DL_WRN, "%s: udev is NULL!\n", __func__);
        return OAL_STATUS_FAILURE;
    }

    tmp_buffer = kmalloc(ARTO_USB_EP0_TRAN_SIZE, GFP_KERNEL);
    if (!tmp_buffer) {
        return OAL_STATUS_RESOURCE;
    }

    cmd = (struct artousb_cmd_header*)tmp_buffer;
    data_buf = tmp_buffer + sizeof(struct artousb_cmd_header);

    cmd->magic_head[0] = ARTO_USB_MAGIC_NUM_0;
    cmd->magic_head[1] = ARTO_USB_MAGIC_NUM_1;

    for (i = 0; i < len / ARTO_USB_EP0_MAX_PAYLOAD_SIZE; ++i)
    {
        cmd->len = ARTO_USB_EP0_MAX_PAYLOAD_SIZE;
        cmd->dst_addr = addr + i * ARTO_USB_EP0_MAX_PAYLOAD_SIZE;
        memcpy(data_buf, data + i * ARTO_USB_EP0_MAX_PAYLOAD_SIZE, cmd->len);
        //printk("0x%08llx 0x%08x %d\n", cmd->dst_addr, *(unsigned int*)data, cmd->len);
        ret = usb_send_control_msg(udev->usbdev, ARTO_USB_DNLD_VENDOR_CMD, 0, 0x0, tmp_buffer, ARTO_USB_EP0_TRAN_SIZE, timeout);
        if (ret < 0)
        {
            AR_DBG(DM_USB, DL_ERR, "usb_send_control_msg transfer failed! ret: %d timeout: %d\n", ret, timeout);
        }
    }

    cmd->len = (unsigned short)(len - i * ARTO_USB_EP0_MAX_PAYLOAD_SIZE);
    if (cmd->len || 0 == i)
    {
        cmd->dst_addr = addr + i * ARTO_USB_EP0_MAX_PAYLOAD_SIZE;
        memcpy(data_buf, data + i * ARTO_USB_EP0_MAX_PAYLOAD_SIZE, cmd->len);

        //if (4 == cmd->len && 0 == (cmd->dst_addr & 0x3))
        //    printk("0x%08llx 0x%08x %d\n", cmd->dst_addr, *(unsigned int*)data, cmd->len);

        ret = usb_send_control_msg(udev->usbdev, ARTO_USB_DNLD_VENDOR_CMD, 0, 0x0, tmp_buffer, cmd->len + sizeof(struct artousb_cmd_header), timeout);
        if (ret < 0)
        {
            AR_DBG(DM_USB, DL_ERR, "usb_send_control_msg transfer failed!\n");
        }
    }

    kfree(tmp_buffer);

    if (ret < 0) {
        status = OAL_STATUS_FAILURE;
    }

    return status;
}

/**
 * @brief Write firmware download done. Rgegister as API ar_usb_send_dnld_done.
 *
 *  @param handle  Refers to oal_handle.
 *  @param timeout Timeout in unit ms
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_usb_send_dnld_done(IN oal_handle *handle, u16 timeout)
{
    oal_status status = OAL_STATUS_SUCCESS;
    int i, ret, tmp = 0;
    struct artousb_cmd_header* cmd = NULL;
    unsigned char* data_buf = NULL;
    unsigned char *tmp_buffer = NULL;
    struct ar_udev *udev = NULL;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    if (!handle) {
        AR_DBG(DM_USB, DL_WRN, "%s: handle is NULL!\n", __func__);
        return OAL_STATUS_FAILURE;
    }

    udev = (struct ar_udev *)handle->card;
    if (udev == NULL) {
        AR_DBG(DM_USB, DL_WRN, "%s: udev is NULL!\n", __func__);
        return OAL_STATUS_FAILURE;
    }

    tmp_buffer = kmalloc(ARTO_USB_EP0_TRAN_SIZE, GFP_KERNEL);
    if (!tmp_buffer) {
        return OAL_STATUS_RESOURCE;
    }

    cmd = (struct artousb_cmd_header*)tmp_buffer;
    data_buf = tmp_buffer + sizeof(struct artousb_cmd_header);

    cmd->magic_head[0] = ARTO_USB_MAGIC_NUM_0;
    cmd->magic_head[1] = ARTO_USB_MAGIC_NUM_1;

    //send exit artousb
    cmd->len = ARTO_USB_EXIT_LEN;
    cmd->dst_addr = ARTO_USB_EXIT_ADDR;
    ret = usb_send_control_msg(udev->usbdev, ARTO_USB_DNLD_VENDOR_CMD, 0, 0x0, tmp_buffer, cmd->len + sizeof(struct artousb_cmd_header), timeout);
    if (ret < 0)
    {
        AR_DBG(DM_USB, DL_ERR, "usb_send_control_msg transfer failed!\n");
    }

    kfree(tmp_buffer);

    if (ret < 0) {
        status = OAL_STATUS_FAILURE;
    }

    return status;
}

/**
 * @brief Update the type of the handle. Rgegister as API oal_update_card_type.
 *
 *  @param handle  Refers to oal_handle.
 *  @param card    Diffferent kind of card priv structure.
 *
 *  @return        N/A.
 */
static void
ar_usb_update_card_type(oal_handle *handle, void *card)
{
    struct ar_udev *cardp = (struct ar_udev *)card;
    if (cardp) {
        cardp->handle = handle;
    }
}

#if (AR_BUS_PM == 1)
void ar_usb_set_sys_pm_status(void *ar_udev, bool flag)
{
    struct ar_udev *udev = (struct ar_udev*) ar_udev;

    udev->sys_suspend = flag;

    AR_DBG(DM_USB, DL_WRN, "%s: %d", __func__, udev->sys_suspend);
}

void ar_usb_resume_wait_and_reset(void *ar_udev)
{
    struct ar_udev *udev = (struct ar_udev*) ar_udev;

    while (wait_for_completion_timeout(&udev->resume_wait, msecs_to_jiffies(1000))) {
        reinit_completion(&udev->resume_wait);
        AR_DBG(DM_USB, DL_WRN, "%s", __func__);
    }
}

static int
ar_usb_suspend(struct usb_interface *intf, pm_message_t message)
{
    //struct usb_device *usb = interface_to_usbdev(intf);
    oal_handle *handle = NULL;
    struct ar_udev *udev = (struct ar_udev *)usb_get_intfdata(intf);
    int ret = 0;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    handle = (oal_handle*)(udev->handle);
    if (!handle) {
        AR_DBG(DM_USB, DL_ERR, "%s: handle is NULL!\n", __func__);
        return 0;
    }

    while (((uint64_t)ktime_to_ms(ktime_sub(ktime_get(), udev->cmd_start_time))) < 1000) {
        msleep(1000);
        AR_DBG(DM_USB, DL_WRN, "%s: wait sys suspend", __func__);
    }

    //1. update state
    ar_usb_set_state(udev, AR_USB_STATE_SLEEP);

    //2. wait msgout complete
    // ret = usb_wait_anchor_empty_timeout(&udev->msgout_anchor, 1000);
    // if (ret == 0) {
    //     AR_DBG(DM_USB, DL_WRN, "%s: wait timeout!", __func__);
    // } else {
    //     AR_DBG(DM_USB, DL_WRN, "%s: wait succ!", __func__);
    // }

    //3. Notify FW PM change
    if (udev->sys_suspend == 1) {
        udev->suspend_s_cnt++;
        /* TBD: Notify rpc */
    }

    //4. recall in urb
    ar_usb_cancel_all_urbs(udev);

    //urb statistic
    //todo : check all return
    AR_DBG(DM_USB, DL_WRN, "%s: pktout_free=%d | pktin_free=%d, pktin_pend=%d, pktin_pendcount=%d | msgout=%d\n",
           __func__, handle->pktout_freecount, handle->pktin_freecount, handle->pktin_pendcount,
           handle->pktin_pendcount, atomic_read(&(handle->msgout_cnt)));

    //5. enable a device to wake up the system
    device_set_wakeup_enable(udev->dev, true);

    //6. auto pm stats
    udev->suspend_d_cnt++;
    udev->suspend_start_time = ktime_get();

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);

    return 0;
}

static int
ar_usb_resume(struct usb_interface *intf)
{
    //struct usb_device *usb = interface_to_usbdev(intf);
    struct ar_udev *udev = (struct ar_udev *)usb_get_intfdata(intf);
    oal_handle *handle = NULL;
    #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    ktime_t diff;
    #endif

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    handle = (oal_handle*)(udev->handle);
    if (!handle) {
        AR_DBG(DM_USB, DL_ERR, "%s: handle is NULL!\n", __func__);
        return 0;
    }

    //1. re-fill urb
    if (handle->callbacks.oal_try_recv_pend) {
        handle->callbacks.oal_try_recv_pend(handle);
    }

    //2. Notify FW PM change
    /* TBD: Notify rpc */

    //3. update state
    ar_usb_set_state(udev, AR_USB_STATE_UP);

    //4. disable a device to wake up the system
    device_set_wakeup_enable(udev->dev, false);

    if (udev->sys_suspend == 1) {
        complete(&udev->resume_wait);
    }

    //5. urb statistic
    //todo: check in-urb number and resumit free queue in-urb
    AR_DBG(DM_USB, DL_WRN, "%s: pktout_free=%d | pktin_free=%d, pktin_pend=%d, pktin_pendcount=%d | msgout=%d\n",
           __func__, handle->pktout_freecount, handle->pktin_freecount, handle->pktin_pendcount,
           handle->pktin_pendcount, atomic_read(&(handle->msgout_cnt)));

    //6. auto pm stats
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    diff = ktime_sub(ktime_get(), udev->suspend_start_time);
    udev->suspend_ttl_time = ktime_add(udev->suspend_ttl_time, diff);
#else
    udev->suspend_ttl_time += ktime_sub(ktime_get(), udev->suspend_start_time);
#endif
    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);

    return 0;
}

static int
ar_usb_reset_resume(struct usb_interface *intf)
{
    struct ar_udev *udev = (struct ar_udev *)usb_get_intfdata(intf);

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);
    AR_DBG(DM_USB, DL_WRN, "[auto]: warning - reset resume");

    //1. usb resume
    ar_usb_resume(intf);

    //2. auto pm stats
    udev->reset_resume_cnt++;

    return 0;
}
#endif

/** Define of usb driver */
static struct usb_driver ar_usbdrvr = {
    .name = KBUILD_MODNAME,
    .probe = ar_usb_probe,
    .disconnect = ar_usb_disconnect,
    .id_table = ar_usb_id_tbl,
#if (AR_BUS_PM == 1)
    .suspend = ar_usb_suspend,
    .resume = ar_usb_resume,
    .reset_resume = ar_usb_reset_resume,
    .supports_autosuspend = true,
#endif
    //.soft_unbind = 1,
    .disable_hub_initiated_lpm = 1,
};

/**
 * @brief Register the usb device.
 *
 *  @return        Linux err code.
 */
static int
ar_usb_register(void)
{
    int ret = 0;

    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    g_usb_registered = true;

    ret = usb_register(&ar_usbdrvr);

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);

    return ret;
}

/**
 * @brief Insmod the usb device.
 *
 *  @return        Linux err code.
 */
int
usb_init(void)
{
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    AR_DBG(DM_GENERIC, DL_INF, "[%s:%d] now calling register USB\n",__func__,__LINE__);

    /* register the bus */
    if (ar_usb_register() < 0) {
        AR_DBG(DM_GENERIC, DL_INF, "usb register failed!\n");
        AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);
        return -ENODEV;
    }

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);

    return 0;
}

/**
 * @brief Unregister the usb device.
 *
 *  @return        N/A.
 */
static void
ar_usb_exit(void)
{
    AR_DBG(DM_USB, DL_INF, AR_FN_ENTRY_STR);

    g_usb_registered = false;
    usb_deregister(&ar_usbdrvr);

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);
}

/**
 * @brief Rmmod the usb device.
 *
 *  @return        N/A.
 */
void
usb_exit(void)
{
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    AR_DBG(DM_GENERIC, DL_INF, "[%s:%d] Now calling usb exit!\n", __func__,__LINE__);

    ar_usb_exit();

    AR_DBG(DM_USB, DL_INF, AR_FN_LEAVE_STR);
}
